package components;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import stdp_rules.STDP_Rule;
import utils.StateCamera;
import utils.math_utils.Coordinate;

/**
 * The abstract super-class of all neurons, ensuring that all classes that use
 * neurons can depend on them having the same available methods which perform
 * roughly the same function.
 * 
 * @author zach
 *
 */
public abstract class AbstractNeuron {

	/** Coordinates if spatially organized. */
	private Coordinate coordinates;
	
	/** Index inside an array or layer (class or metaphorical).*/
	private int index;
	
	/** A reference to the camera that monitors this neurons state.*/
	private StateCamera recorder;
	
	/** Does this neuron report to its recorder?. */
    protected boolean reporter;
    
    /** A list of the afferent synapses to this neuron. */
	private List<Synapse> fanOut =
		Collections.synchronizedList(new ArrayList<Synapse>());
	
	/** A list of efferent synapses to this neuron. */
	private List<Synapse> fanIn =
		Collections.synchronizedList(new ArrayList<Synapse>());
	
    /** Last time this neuron spiked. */
    private double lastSpike = 0;
	
	/** The integration time constant of this neuron. */
	private double timeConstant;
	
	/** Is this neuron inhibitory or excitatory. */
	protected final boolean excitatory;
	
	/** A reference to the object governing stdp for this neuron's synapses .*/
	private STDP_Rule stdp;
	
	/** Is STDP active for this neuron's synapses? .*/
	protected boolean stdpOn;
	
	/** The current time in the simulation (ms). */
	protected double time;
	

	
	/**
	 * A standard constructor requiring only very basic and necessary
	 * information for a neuron to have.
	 * @param timeConstant this neuron's integration time constant.
	 * @param excitatory is this neuron excitatory or inhibitory?
	 * @param index this neuron's index in its array/layer
	 */
	public AbstractNeuron(final double timeConstant,
			final boolean excitatory, final int index) {
		this.index = index;
		this.timeConstant = timeConstant;
		this.excitatory = excitatory;
	}
	
	/**
	 * A very skeletal constructor, used when all other values are subject
	 * to randomization.
	 * @param excitatory is this neuron excitatory?
	 * @param index the index of this neuron in its parent array or layer.
	 */
	public AbstractNeuron(final boolean excitatory, final int index) {
		this.index = index;
		this.excitatory = excitatory;
	}
	
	/**
	 * A method for updating this neuron. This method invokes (a) differential
	 * equation(s) and integrates them forward in time based on the specified
	 * timestep and differential equation(s).
	 * @param timeStep the timestep of integration also updates this neuron's
	 * time variable used for keeping the neurons in sync.
	 * @return true if the neuron spiked.
	 */
	public abstract boolean update(double timeStep);
	
	/**
	 * A method which causes the neuron to spike. Sending signals to is 
	 * synapses as appropriate. This method is to be used internally only.
	 */
	protected abstract void spike();

	
	/*/*******************************************************************
	 * 						GETTERS AND SETTERS							 *
	 *********************************************************************/
	
	public List<Synapse> getFanOut() {
		return fanOut;
	}

	public void setFanOut(ArrayList<Synapse> fanOut) {
		this.fanOut = fanOut;
	}

	public List<Synapse> getFanIn() {
		return fanIn;
	}

	public void setFanIn(ArrayList<Synapse>  fanIn) {
		this.fanIn = fanIn;
	}
	
	public boolean isExcitatory() {
		return excitatory;
	}
	
	public double getTimeConstant() {
		return timeConstant;
	}

	public double getTime() {
		return time;
	}


	public void setTime(double time) {
		this.time = time;
	}
	
	public int getIndex() {
		return index;
	}


	public void setTimeConstant(double timeConstant) {
		this.timeConstant = timeConstant;
	}


	public Coordinate getCoordinates() {
		return coordinates;
	}


	public void setCoordinates(Coordinate xyz) {
		this.coordinates = xyz;
	}

	public void setCoordinates(int x, int y, int z) {
		int [] co = {x, y, z};
		coordinates = new Coordinate(co);
	}

	public StateCamera getRecorder() {
		return recorder;
	}

	public void setRecorder(StateCamera recorder) {
		this.recorder = recorder;
	}

	public boolean isReporter() {
		return reporter;
	}

	public void setReporter(boolean report) {
		this.reporter = report;
	}

	public void setIndex(int index) {
		this.index = index;
	}

	public boolean isStdpOn() {
		return stdpOn;
	}

	public void setStdpOn(boolean stdpOn) {
		this.stdpOn = stdpOn;
	}

	public STDP_Rule getStdp() {
		return stdp;
	}

	public void setStdp(STDP_Rule stdp) {
		this.stdp = stdp;
	}
	
	public double getLastSpike() {
		return lastSpike;
	}

	public void setLastSpike(double lastSpike) {
		this.lastSpike = lastSpike;
	}
	
	public int getNumSynapses() {
		int syns = 0;
		if(fanIn != null && !fanIn.isEmpty()) {
			syns += fanIn.size();
		}
		if(fanOut != null && !fanOut.isEmpty()) {
			syns += fanOut.size();
		}
		return syns;
	}
	
}
